﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
using System.Configuration;

using SlimDX;
using SlimDX.XAudio2;
using SlimDX.Multimedia;

using LibSnesDotNet.Core;
using LibSnesDotNet.Threading;

using Configuration = System.Configuration.Configuration;

namespace LibSnesDotNet.Audio
{
	class XAudio2Driver : IAudioDriver
	{
		const int BufferCount = 2;

		WaitableBool   _isBusy;

		XAudio2        _device;
		MasteringVoice _masterVoice;
		SourceVoice    _sourceVoice;
		AudioBuffer    _audioBuffer;
		byte[][]       _buffers;
		DataStream[]   _bufferStreams;
		int            _bufferCursor;
		bool           _isPaused;

		public XAudio2Driver(Configuration config)
		{
			IsDisposed = false;

			try
			{
				_isBusy = new WaitableBool(false);

				_device      = new XAudio2();
				_masterVoice = new MasteringVoice(_device);
				_sourceVoice = new SourceVoice(_device,
				                               new WaveFormat()
				                               {
					                               FormatTag             = WaveFormatTag.Pcm,
					                               Channels              = 2,
					                               BitsPerSample         = 16,
					                               SamplesPerSecond      = 32040,
					                               AverageBytesPerSecond = 2 * (16 / 8) * 32040,
					                               BlockAlignment        = 2 * (16 / 8)
				                               },
				                               VoiceFlags.None, 2.0F);
				_sourceVoice.BufferStart += (s, e) =>
				{
					if (_sourceVoice.State.BuffersQueued < BufferCount)
					{
						_isBusy.Value = false;
					}
				};

				_buffers       = new byte[BufferCount][];
				_bufferStreams = new DataStream[BufferCount];
				for (int i = 0; i < BufferCount; i++)
				{
					_buffers[i]       = new byte[Snes.MaxAudioBufferLength * 4];
					_bufferStreams[i] = new DataStream(_buffers[i], true, false);
				}

				_bufferCursor = 0;
				_audioBuffer = new AudioBuffer();
				_isPaused = true;
			}
			catch
			{
				Dispose();
				throw;
			}
		}

		public IWaitableBool IsBusy { get { return new ReadOnlyWaitableBool(_isBusy); } }

		public byte[] GetNextBuffer()
		{
			return _buffers[_bufferCursor];
		}

		public void Play(AudioUpdatedEventArgs e)
		{
			while (!TryPlay(e))
			{
				_isBusy.WaitWhile();
			}
		}

		public bool TryPlay(AudioUpdatedEventArgs e)
		{
			AssertUndisposed();

			if (!_isBusy.TryAcquire(true)) { return false; }

			if (_isPaused)
			{
				_sourceVoice.Start();
				_isPaused = false;
			}

			_audioBuffer.AudioData = _bufferStreams[_bufferCursor];
			_audioBuffer.AudioBytes = (int)e.SampleCount * 4;
			e.AudioBuffer.ReadRange<byte>(_buffers[_bufferCursor], 0, _audioBuffer.AudioBytes);
			_sourceVoice.SubmitSourceBuffer(_audioBuffer);
			_bufferCursor = _bufferCursor >= BufferCount -1 ? 0 : _bufferCursor + 1;

			if (_sourceVoice.State.BuffersQueued >= BufferCount)
			{
				_isBusy.Value = true;
			}

			if (_sourceVoice.State.BuffersQueued < BufferCount)
			{
				_isBusy.Value = false;
			}

			return true;
		}

		public void Pause()
		{
			AssertUndisposed();

			_isPaused = true;
			_sourceVoice.Stop();
		}

		public bool IsDisposed { get; private set; }

		public void Dispose()
		{
			AssertUndisposed();
			IsDisposed = true;

			_sourceVoice.Stop();
			_sourceVoice.Dispose();
			_audioBuffer.Dispose();
			_masterVoice.Dispose();
			_device.Dispose();
		}

		public void AssertUndisposed()
		{
			if (IsDisposed) { throw new ObjectDisposedException(ToString()); }
		}
	}
}
